
#ifndef PING_H
#define PING_H

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <linux/ip.h>
#include <linux/icmp.h>
#include <unistd.h>
#include <iostream>
#include <string>
#include <cstring>
#include <net/if.h>
#include <sys/ioctl.h>
#include "Utility.h"

#define NUM_ECHO_REQUESTS 4
#define ECHO_TIMEOUT 3
#define ECHO_INTERVAL 1


int Ping(std::string dstAddr)
{
    struct iphdr *ip, *ip_reply;
    struct icmphdr *icmp, *icmp_reply;
    struct sockaddr_in connection;
    char* packet;
    char* buffer;
    int sockfd;
    
	std::string srcAddr;
	GetSourceIP(srcAddr);

	std::cout << "Source address: " << srcAddr << std::endl;
	std::cout << "Destination address: " << dstAddr << std::endl << std::endl;
    
  	packet = new char[sizeof(struct iphdr) + sizeof(struct icmphdr)];
	buffer = new char[sizeof(struct iphdr) + sizeof(struct icmphdr)];

    ip = (struct iphdr*) packet;
    icmp = (struct icmphdr*) (packet + sizeof(struct iphdr));


	//
	// The ICMP packet
	//
    icmp->type			= ICMP_ECHO;
    icmp->code			= 0; // error message code, none = 0
    icmp->un.echo.id		= 0;
    icmp->un.echo.sequence	= 0; // sequence # if more than one are sent
    icmp-> checksum		= in_cksum((unsigned short *)icmp, sizeof(struct icmphdr));

	//
	// The IP header
	//
    ip->ihl			= 5; // header length = 5*4 = 20 bytes
    ip->version			= 4; // IPv4
    ip->tos			= 0; // normal service
    ip->tot_len			= sizeof(struct iphdr) + sizeof(struct icmphdr);
    ip->id			= htons(random()); // single datagram, ID doesn't matter
    ip->ttl			= 255; // max is 255
    ip->protocol		= IPPROTO_ICMP;
    ip->saddr			= inet_addr(srcAddr.c_str());
    ip->daddr			= inet_addr(dstAddr.c_str());
    ip->check			= in_cksum((unsigned short *)ip, sizeof(struct iphdr));
 
    

   
    connection.sin_family = AF_INET;
    connection.sin_addr.s_addr = inet_addr(dstAddr.c_str());
	socklen_t addrlen;

	int selectVal;
	for(unsigned int echoCounter = 0; echoCounter < NUM_ECHO_REQUESTS; ++echoCounter)	
	{
		sleep(ECHO_INTERVAL);

		//
		// create raw socket each iteration
		// otherwise the server doesn't seem to respond
		// something to do with the port, echo.id and echo.seq
		//
		if ((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)) == -1)
		{
			perror("socket");
			exit(EXIT_FAILURE);
		}
		
		// notify the lower layers of the custom headers    
		int optval = 1;
		if(setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &optval, sizeof(int)) == -1)
		{
			std::cout << "Unable to set the IP_HDRINCL option. Exiting..." << std::endl;
			close(sockfd);
			return -1;
		}


  		// send packet   
		int snd = sendto(sockfd, packet, ip->tot_len, 0, (struct sockaddr *)&connection, sizeof(struct sockaddr));
		if(snd == -1)
		{
			perror("sendto");
			close(sockfd);
			return -1;
		}
		else
			std::cout << "Sent " << snd << " bytes of attempt# " << echoCounter << " to " << dstAddr << std::endl;
		
		fd_set fds;
		FD_ZERO(&fds);
		FD_SET(sockfd, &fds);
   		struct timeval tv;
		tv.tv_sec = ECHO_TIMEOUT;
		tv.tv_usec = 0;
		selectVal = select(sockfd+1, &fds, NULL, NULL, &tv);
		if(selectVal == -1)
		{
			perror("select");
			close(sockfd);
			return -1;
		}
		else if(selectVal == 0)
		{
			std::cout << "Timeout occured for attempt# " << echoCounter << std::endl;
			continue;
		}
		
		addrlen = sizeof(connection);
		int recv = recvfrom(sockfd, buffer, sizeof(struct iphdr) + sizeof(struct icmphdr), 0, (struct sockaddr *)&connection, &addrlen);
		if (recv == -1)
		{
			perror("recv");
		}
		else if (recv == 0)
		{
			std::cout << "Remote side closed the socket!" << std::endl;
    		close(sockfd);
			return -1;
		}
		else
		{
			std::cout << "Received " << recv << " byte reply from " << inet_ntoa(((struct sockaddr_in*)&connection)->sin_addr) << std::endl;
			ip_reply = (struct iphdr*) buffer;
			icmp_reply = (struct icmphdr*) (buffer + sizeof(struct iphdr));
			//std::cout << "ID: " << ntohs(ip_reply->id) << std::endl;
			std::cout << "TTL: " << (int)ip_reply->ttl << std::endl;
			std::cout << "ICMP type: " << (int)icmp_reply->type << std::endl;
		}
    	close(sockfd);
	}

	// delete the dynamically allocated memory
	delete[] packet;
	delete[] buffer;

    return 0;
}



#endif // PING_H
